package com.wang.mall.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.wang.mall.bean.SmsSeckillSession;
import com.wang.mall.bean.SmsSeckillSkuRelation;
import com.wang.mall.service.ProductService;
import com.wang.mall.service.SkillService;
import com.wang.mall.service.SmsSeckillSessionService;
import com.wang.mall.to.SeckillSkuRedisTo;
import com.wang.mall.to.rabbitmq.SeckillOrderTo;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Service
public class SkillServiceImpl implements SkillService {

    @Autowired
    RedissonClient redissonClient;

    @Autowired
    private ProductService productService;

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    SmsSeckillSessionService smsSeckillSessionService;


    @Autowired
    RabbitTemplate rabbitTemplate;

    // 秒杀活动前缀
    private final String SESSIONS_CACHE_PREFIX = "seckill:sessions:";
    // 参与秒杀的商品前缀
    private final String SKUKILL_CACHE_PREFIX = "seckill:skus:";
    // 分布式信号量作为秒杀数量的前缀
    private final String SKU_STOCK_SEMAPHORE = "seckill:stock:";

    @Override
    public void uploadSkillProduct() {
        // 扫描最近三天哪些活动需要参与秒杀
        List<SmsSeckillSession> sessionList = smsSeckillSessionService.uploadSkillProduct();
        // 将活动信息缓存到redis中

        if (!CollectionUtils.isEmpty(sessionList)) {
            Long now = System.currentTimeMillis();

            // 缓存活动信息
            saveSessionInfo(sessionList, now);
            // 缓存活动商品信息
            saveSessionProductInfo(sessionList, now);
        }
    }


    // 缓存活动信息
    private void saveSessionInfo(List<SmsSeckillSession> sessionList, Long now) {


        sessionList.forEach(smsSeckillSession -> {
            long startTime = smsSeckillSession.getStartTime().getTime();
            long endTime = smsSeckillSession.getEndTime().getTime();

            // 如果结束时间大于当前的时间,再将数据添加到redis
            if (endTime > System.currentTimeMillis()) {
                String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
                Boolean aBoolean = redisTemplate.hasKey(key);
                if (!aBoolean) { // 判断秒杀信息在redis中是否存在
                    List<String> productId = smsSeckillSession.getList().stream().map(smsSeckillSkuRelation -> {
                        return smsSeckillSession.getId() + "_" + String.valueOf(smsSeckillSkuRelation.getSkuId());
                    }).collect(Collectors.toList());

                    // 缓存活动信息
                    redisTemplate.opsForList().leftPushAll(key, productId);
                    redisTemplate.expire(key, endTime - now, TimeUnit.MILLISECONDS);
                }
            }


        });
    }

    // 缓存活动商品信息
    private void saveSessionProductInfo(List<SmsSeckillSession> sessionList, Long now) {
        sessionList.forEach(smsSeckillSession -> {

            long startTime = smsSeckillSession.getStartTime().getTime();
            long endTime = smsSeckillSession.getEndTime().getTime();
            // 如果结束时间大于当前的时间,再将数据添加到redis
            if (endTime > System.currentTimeMillis()) {
                BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<SmsSeckillSkuRelation> list = smsSeckillSession.getList();
                list.forEach(smsSeckillSkuRelation -> {
                    String randomCode = UUID.randomUUID().toString().replace("-", "");
                    String redisKey = smsSeckillSkuRelation.getPromotionSessionId().toString() + "_" + smsSeckillSkuRelation.getSkuId().toString();
                    // 判断秒杀商品在redis中是否存在

                    if (!hashOps.hasKey(redisKey)) {

                        SeckillSkuRedisTo seckillSkuRedisTo = new SeckillSkuRedisTo();
                        BeanUtils.copyProperties(smsSeckillSkuRelation, seckillSkuRedisTo);
                        seckillSkuRedisTo.setStartTime(smsSeckillSession.getStartTime().getTime());
                        seckillSkuRedisTo.setEndTime(smsSeckillSession.getEndTime().getTime());

                        seckillSkuRedisTo.setProduct(productService.findById(smsSeckillSkuRelation.getSkuId()));

                        seckillSkuRedisTo.setRandomCode(randomCode);


                        // 保存秒杀商品
                        String s = JSON.toJSONString(seckillSkuRedisTo);
                        hashOps.put(redisKey, s);
                        hashOps.expire(endTime - now, TimeUnit.MILLISECONDS);


                        // 设置分布式信号量,作为商品的库存信息
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                        // 商品可以秒杀的数量作为信号量
                        semaphore.trySetPermits(smsSeckillSkuRelation.getSeckillCount());
                        semaphore.expire(endTime - now, TimeUnit.MILLISECONDS);
                    }


                });
            }


        });
    }


    /**
     * 获取当前可以参与秒杀的商品信息
     *
     * @return
     */
    @Override
    public List<SeckillSkuRedisTo> getCurrentSkillProduct() {
        // 1.确定当前时间属于哪个秒杀场次
        long now = System.currentTimeMillis();
        Set<String> keys = redisTemplate.keys(SESSIONS_CACHE_PREFIX + "*");
        for (String key : keys) {
            String[] s = key.replace(SESSIONS_CACHE_PREFIX, "").split("_");
            Long startTime = Long.valueOf(s[0]);
            Long endTime = Long.valueOf(s[1]);
            // 判断当前时间是否在某项活动的秒杀时间内
            if (now >= startTime && now <= endTime) {
                // 2.获取当前场次秒杀场次需要的所有的商品信息
                List<String> range = redisTemplate.opsForList().range(key, 0, -1);
                BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
                List<String> list = hashOps.multiGet(range);
                if (!CollectionUtils.isEmpty(list)) {
                    return list.stream().map(item -> {
                        SeckillSkuRedisTo seckillSkuRedisTo = JSON.parseObject(item.toString(), SeckillSkuRedisTo.class);
                        // 获取当前秒杀的商品还剩余几件
                        String randomCode = seckillSkuRedisTo.getRandomCode();
                        Long surplus = Long.valueOf(redisTemplate.opsForValue().get(SKU_STOCK_SEMAPHORE + randomCode));
                        seckillSkuRedisTo.setSurplus(surplus);
//                        seckillSkuRedisTo.setRandomCode(null);  当前秒杀已经开始了
                        return seckillSkuRedisTo;
                    }).collect(Collectors.toList());
                }
                break;
            }
        }


        return null;
    }

    /**
     * 获取当前的秒杀商品详情
     *
     * @param productId
     * @return
     */
    @Override
    public SeckillSkuRedisTo productSkillInfo(Long productId, Integer session) {
        // 找到所有需要参与秒杀的key信息
        BoundHashOperations<String, String, String> operations = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);

        String s = operations.get(session + "_" + productId);
        if (!StringUtils.isEmpty(s)) {
            SeckillSkuRedisTo to = JSON.parseObject(s, SeckillSkuRedisTo.class);
            Long surplus = Long.valueOf(redisTemplate.opsForValue().get(SKU_STOCK_SEMAPHORE + to.getRandomCode()));
            to.setSurplus(surplus);
            Long startTime = to.getStartTime();


            long l = System.currentTimeMillis();
            if (l >= startTime && l <= to.getEndTime()) {

            } else {
                to.setRandomCode(null);
            }
            return to;
        }

        return null;
    }

    /**
     * 开始秒杀
     *
     * @param productId 商品ID
     * @param sessionId 商品场次
     * @param code      随机码
     * @param count     数量
     * @return
     */
    @Override
    public String kill(Long productId, Integer sessionId, String code, Integer count) {
        // 1.,获取当前商品的详细信息
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUKILL_CACHE_PREFIX);
        String killId = sessionId + "_" + productId;
        Boolean aBoolean1 = hashOps.hasKey(killId);
        System.out.println("aBoolean1 = " + aBoolean1);
        String s = hashOps.get(killId);
        if (!StringUtils.isEmpty(s)) {  // 获取到商品的信息
            SeckillSkuRedisTo skuRedisTo = JSON.parseObject(s, SeckillSkuRedisTo.class);
            // 合法性校验
            // 1.校验秒杀时间是否过期
            Long endTime = skuRedisTo.getEndTime();
            Long startTime = skuRedisTo.getStartTime();
            Long ttl = endTime - startTime;
            long l = System.currentTimeMillis();
            if (l >= startTime && l <= endTime) { // 秒杀时间未过期
                // 校验随机码和商品场次ID
                String randomCode = skuRedisTo.getRandomCode();
                String redisSkillId = skuRedisTo.getPromotionSessionId() + "_" + skuRedisTo.getSkuId();
                if (randomCode.equals(code) && killId.equals(redisSkillId)) { // 匹配上了
                    // 判断购买数量
                    Integer seckillCount = skuRedisTo.getSeckillCount();
                    if (count <= seckillCount) { // 在购买范围内
                        // 验证这个人是否已经购买过 , 秒杀成功就去redis中进行占位,
                        // userId_sessionId_productId
                        Long memberId = Long.valueOf(SecurityContextHolder.getContext().getAuthentication().getPrincipal().toString());
                        String redisKey = memberId + "_" + sessionId + "_" + productId;
                        // 自动过期
                        Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent(redisKey, count.toString(), ttl, TimeUnit.MILLISECONDS);
                        if (aBoolean) {
                            // 占位成功说明从来没有买过,
                            // 消减信号量 , 信号量就是秒杀的商品数量
                            RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                            try {
                                boolean b = semaphore.tryAcquire(count, 100, TimeUnit.MILLISECONDS);
                                if (b) {
                                    // 秒杀成功,
                                    // TODO 快速下单, 给MQ发送消息，快速下单
                                    String orderSn = IdWorker.getTimeId();
                                    SeckillOrderTo seckillOrderTo = new SeckillOrderTo();
                                    seckillOrderTo.setOrderSn(orderSn);
                                    seckillOrderTo.setMemberId(memberId);
                                    seckillOrderTo.setNum(count);
                                    seckillOrderTo.setPromotionSessionId(Long.valueOf(sessionId));
                                    seckillOrderTo.setSeckillPrice(skuRedisTo.getSeckillPrice());
                                    seckillOrderTo.setSkuId(productId);

                                    String s1 = JSONObject.toJSONString(seckillOrderTo);
                                    rabbitTemplate.convertAndSend("wang-order-event-exchange", "order.seckill.order", s1);
                                    return orderSn;
                                }
                            } catch (InterruptedException e) {
                                return null;
                            }
                        }
                    }
                }
            }
        }

        // 以上判断出问题了就返回null
        return null;
    }
}
